##   Reactor.py
##   $Id$
##   Copyright (C) 2007 Sean Lin
##
##   This program is free software; you can redistribute it and/or modify
##   it under the terms of the GNU General Public License as published by
##   the Free Software Foundation; either version 2, or (at your option)
##   any later version.
##
##   This program is distributed in the hope that it will be useful,
##   but WITHOUT ANY WARRANTY; without even the implied warranty of
##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##   GNU General Public License for more details.

import select
import time
from heapq import heappush, heappop
import types

def get_current():
    "return the time, just make the timer_queue and the user of timer_queue use the same time."
    return time.time()


class Timer:
    def __init__(self, timeout, callback, args):
        self.timeout = timeout
        self.callback = callback
        self.args = args

    def __cmp__(self, timer):
        return self.timeout - timer.timeout

    def on_timeout(self):
        arg_type = type(self.args)
        if arg_type == types.TupleType or \
            arg_type == types.ListType:
            self.callback(*self.args)
        elif arg_type == types.DictType:
            self.callback(**self.args)
        else:
            self.callback(self.args)
    
class Timer_Queue:
    "Timer Queue, call the callback object registered at the specified time"
    def __init__(self):
        self.__timer_heap = [] #use heap to store the timers
        self.__timeout = 0
    
    def __len__(self):
        return len(self.__timer_heap)

    def add_timer(self, expire, callback, args):
        timeout = get_current() + expire
        heappush(self.__timer_heap, Timer(timeout,callback,args))

    def get_time_to_wait(self):
        if self.__timer_heap:
            now = get_current()
            if now < self.__timer_heap[0].timeout:
                t = self.__timer_heap[0].timeout - now
                if t > 1:
                    return 1
                else:
                    return t
            else:
                return 0
        else:
            return 1 

    def process(self):
        now = get_current()
        while self.__timer_heap and now >= self.__timer_heap[0].timeout:
            heappop(self.__timer_heap).on_timeout()


class Handler:
    def __init__(self, reactor):
        self.reactor = reactor
        pass
    
    def get_fd(self):
        return self.fd

    def __cmp__(self,handler):
        return self.fd - handler.fd

    def handle_input(self):
        return -1

    def handle_output(self):
        return -1

    def handle_close(self, mask):
        return 0

    pass

READ_MASK = 1
WRITE_MASK = 2
EXCEPTION_MASK = 4

class Reactor:    
    __instance = None
    def __new__(typ, *args, **kwargs):
        if Reactor.__instance == None:
            obj = object.__new__(typ, *args, **kwargs)
            Reactor.__instance = obj        
        return Reactor.__instance

    def __init__(self):
        self.__time_queue = Timer_Queue()
        self.__handlers = {}
        self.__in_fd = []
        self.__out_fd = []
        self.__exception_fd = []
        self.__running = False
        pass
    
    def register_timer(self, expire, callback, args):
        """There's a trick here: when the args is a single tuple, list or dict,
            use () to make it as a tuple.
            reactor.register_timer(10, list_all, ([1,2,3,4]))
            see alse: the implement of the Timer object.
        """
        self.__time_queue.add_timer(expire, callback, args) 
        pass

    def register_handler(self, handler, mask):
        self.__handlers[handler.get_fd()] = handler

        if mask & READ_MASK:
            self.__in_fd.append(handler.get_fd())

        if mask & WRITE_MASK:
            self.__out_fd.append(handler.get_fd())

        if mask & EXCEPTION_MASK:
            self.__exception_fd.append(handler.get_fd())
        pass

    def remove_handler(self, handler, mask):
        
        pass

    def loop_once(self):
        in_ready, out_ready, exception = \
            select.select(self.__in_fd,self.__out_fd,self.__exception_fd,\
                self.__time_queue.get_time_to_wait()\
                )

        for i in in_ready:
            if self.__handlers[i].handle_input() == -1:
                if self.__handlers[i].handle_close(READ_MASK) == 0:
                    del self.__handlers[i]
                del in_ready[in_ready.index(i)]

        for i in out_ready:
            if self.__handlers[i].handle_output() == -1:
                if self.__handlers[i].handle_close(WRITE_MASK) == 0:
                    del self.__handlers[i]
                del out_ready[out_ready.index(i)]
        self.__time_queue.process()

        pass
    
    def run(self):
        self.__running = True
        while self.__running:
            self.loop_once()
        pass

